﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using JasLib.Extensions;

namespace JasLib.Interop.Win32
{
    public static partial class PDH
    {
        /// <param name="szFullCounterPath">
        /// Visit here for counter path information:
        /// http://msdn.microsoft.com/en-us/library/aa373193(v=VS.85).aspx
        /// </param>
        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhAddCounterW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhAddCounter(
            [In] PDH_HQUERY hQuery,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szFullCounterPath,
            [In] UINT_PTR dwUserData,
            [Out] out PDH_HCOUNTER phCounter);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhAddEnglishCounterW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhAddEnglishCounter(
            [In] PDH_HQUERY hQuery,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szFullCounterPath,
            [In] UINT_PTR dwUserData,
            [Out] out PDH_HCOUNTER phCounter);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhBindInputDataSourceW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhBindInputDataSource(
            [Out] out PDH_HLOG phDataSource,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szLogFileNameList);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhBrowseCountersW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhBrowseCounters([In] ref PDH_BROWSE_DLG_CONFIG pBrowseDlgData);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCalculateCounterFromRawValue", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCalculateCounterFromRawValue(
            [In] PDH_HCOUNTER hCounter,
            [In] PdhFormat dwFormat,
            [In] ref PDH_RAW_COUNTER rawValue1,
            [In] ref PDH_RAW_COUNTER rawValue2,
            [Out] out PDH_FMT_COUNTERVALUE fmtValue);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCalculateCounterFromRawValue", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCalculateCounterFromRawValue(
            [In] PDH_HCOUNTER hCounter,
            [In] PdhFormat dwFormat,
            [In] ref PDH_RAW_COUNTER rawValue1,
            [In] UINT_PTR rawValue2,
            [Out] out PDH_FMT_COUNTERVALUE fmtValue);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCloseQuery", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCloseQuery([In] PDH_HQUERY hQuery);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCollectQueryData", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCollectQueryData([In, Out] PDH_HQUERY hQuery);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCollectQueryDataEx", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCollectQueryDataEx(
            [In] PDH_HQUERY hQuery,
            [In] uint dwIntervalTime,
            [In] HANDLE hNewDataEvent);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhCollectQueryDataWithTime", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhCollectQueryDataWithTime(
            [In] PDH_HQUERY hQuery,
            [Out] out long pllTimeStamp);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhConnectMachineW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhComputeCounterStatistics(
            [In] PDH_HCOUNTER hCounter,
            [In] PdhFormat dwFormat,
            [In] uint dwFirstEntry,
            [In] uint dwNumEntries,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] PDH_RAW_COUNTER[] lpRawValueArray,
            [Out] out PDH_STATISTICS data);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhConnectMachineW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhConnectMachine([In, MarshalAs(UnmanagedType.LPWStr)] string szMachineName);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhEnumObjectItemsW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhEnumObjectItems(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szDataSource,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szMachineName,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szObjectName,
            [Out] char[] mszCounterList,
            [In, Out] ref uint pcchCounterListLength,
            [Out] char[] mszInstanceList,
            [In, Out] ref uint pcchInstanceListLength,
            [In] PerformanceCounterDetailLevel dwDetailLevel,
            [In] uint dwFlags);

        /// <summary>
        /// Safe overload for the Win32 PdhEnumObjectItems function that throws an exception on failure.
        /// </summary>
        /// <param name="mszCounterList">A list of the counters supported by this object.</param>
        /// <param name="mszInstanceList">A list of the instances supported by this object,
        /// or null if this object doesn't support multiple instances.</param>
        public static void PdhEnumObjectItems(
            string szDataSource,
            string szMachineName,
            string szObjectName,
            out string[] mszCounterList,
            out string[] mszInstanceList,
            PerformanceCounterDetailLevel dwDetailLevel = PerformanceCounterDetailLevel.PERF_DETAIL_WIZARD)
        {
            PDH_STATUS status;
            int iReferenceSize = 1000;
            uint uiCounterBufferSize;
            char[] counterBuffer;
            uint uiInstanceBufferSize;
            char[] instanceBuffer;

            do
            {
                iReferenceSize *= 2;

                /// We won't know which buffer was too small, so grow them both.
                counterBuffer = new char[iReferenceSize];
                uiCounterBufferSize = (uint)iReferenceSize;

                instanceBuffer = new char[iReferenceSize];
                uiInstanceBufferSize = (uint)iReferenceSize;
            }
            while ((status = PdhEnumObjectItems(
                szDataSource, szMachineName, szObjectName,
                counterBuffer, ref uiCounterBufferSize, instanceBuffer, ref uiInstanceBufferSize,
                dwDetailLevel, 0)) == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException();

            if (uiCounterBufferSize > 0)
                mszCounterList = InteropUtilities.SplitDoubleNullTerminated(counterBuffer, (int)uiCounterBufferSize).ToArray();
            else
                mszCounterList = null;

            if (uiInstanceBufferSize > 0)
                mszInstanceList = InteropUtilities.SplitDoubleNullTerminated(instanceBuffer, (int)uiInstanceBufferSize).ToArray();
            else
                mszInstanceList = null;

            return;
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhEnumObjectsW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhEnumObjects(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szDataSource,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szMachineName,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] char[] mszObjectList,
            [In, Out] ref uint pcchBufferLength,
            [In] PerformanceCounterDetailLevel dwDetailLevel,
            [In, MarshalAs(UnmanagedType.Bool)] bool bRefresh);

        /// <summary>
        /// Safe overload for the Win32 PdhEnumObjects function that throws an exception on failure.
        /// </summary>
        public static IEnumerable<string> PdhEnumObjects(
            string szDataSource = null,
            string szMachineName = null,
            PerformanceCounterDetailLevel dwDetailLevel = PerformanceCounterDetailLevel.PERF_DETAIL_WIZARD,
            bool bRefresh = true)
        {
            PDH_STATUS status;
            int iReferenceSize = 2500;
            uint uiSize;
            char[] buffer;

            do
            {
                iReferenceSize *= 2;

                buffer = new char[iReferenceSize];
                uiSize = (uint)iReferenceSize;
            }
            while ((status = PdhEnumObjects(szDataSource, szMachineName, buffer, ref uiSize, dwDetailLevel, bRefresh)) == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException();

            return InteropUtilities.SplitDoubleNullTerminated(buffer, (int)uiSize);
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhExpandCounterPathW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhExpandCounterPath(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szWildCardPath,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] char[] mszExpandedPathList,
            [In, Out] ref uint pcchPathListLength);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhExpandWildCardPathW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhExpandWildCardPath(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szDataSource,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szWildCardPath,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] char[] mszExpandedPathList,
            [In, Out] ref uint pcchPathListLength,
            [In] PdhExpandWildCardPathFlags dwFlags);

        /// <summary>
        /// Safe overload for the Win32 PdhExpandWildCardPath function that throws an exception on failure.
        /// </summary>
        public static IEnumerable<string> PdhExpandWildCardPath(
            string szDataSource,
            string szWildCardPath,
            PdhExpandWildCardPathFlags dwFlags = 0)
        {
            PDH_STATUS status;
            uint uiBufferLength = 0;

            status = PdhExpandWildCardPath(szDataSource, szWildCardPath, null, ref uiBufferLength, dwFlags);
            if (status !=  PDH_STATUS.PDH_MORE_DATA)
                status.TryThrowPdhException("PdhExpandWildCardPath(szDataSource = \"{0}\", szWildCardPath = \"{1}\", dwFlags = ({2})) failed.", szDataSource, szWildCardPath, dwFlags);

            char[] buffer = new char[uiBufferLength];

            status = PdhExpandWildCardPath(szDataSource, szWildCardPath, buffer, ref uiBufferLength, dwFlags);
            status.TryThrowPdhException("PdhExpandWildCardPath(szDataSource = \"{0}\", szWildCardPath = \"{1}\", dwFlags = ({2})) failed.", szDataSource, szWildCardPath, dwFlags);

            return InteropUtilities.SplitDoubleNullTerminated(buffer, (int)uiBufferLength);
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhFormatFromRawValue", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhFormatFromRawValue(
            [In] PerformanceCounterType dwCounterType,
            [In] PdhFormat dwFormat,
            [In] ref long pTimeBase,
            [In] ref PDH_RAW_COUNTER rawValue1,
            [In] ref PDH_RAW_COUNTER rawValue2,
            [Out] out PDH_FMT_COUNTERVALUE fmtValue);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetCounterInfoW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public unsafe static extern PDH_STATUS PdhGetCounterInfo(
            [In] PDH_HCOUNTER hCounter,
            [In] BOOLEAN bRetrieveExplainText,
            [In, Out] ref uint pdwBufferSize,
            [In] byte* lpBuffer);

        /// <summary>
        /// Safe overload for the Win32 PdhGetCounterInfo function that throws an exception on failure.
        /// </summary>
        public static void PdhGetCounterInfo(
            PDH_HCOUNTER hCounter,
            bool bRetrieveExplainText,
            out PDH_COUNTER_INFO.Managed info)
        {
            PDH_STATUS status = 0;
            uint uiItemCount;
            uint uiBufferSize;
            byte[] buffer;

            info = new PDH_COUNTER_INFO.Managed();

            do
            {
                unsafe
                {
                    /// Get the size needed for the buffer.
                    uiBufferSize = 0;
                    status = PdhGetCounterInfo(hCounter, bRetrieveExplainText ? BOOLEAN.TRUE : BOOLEAN.FALSE,  ref uiBufferSize, null);
                    if (status != PDH_STATUS.PDH_MORE_DATA)
                        break;

                    buffer = new byte[uiBufferSize];

                    /// What makes this function difficult is not that string data is packed after the array of values,
                    /// but that it is referenced by absolute pointers inside the struct,
                    /// which means we have to keep alive the pointer to the buffer until we are finished with everything.
                    fixed (byte* pBuffer = buffer)
                    {
                        status = PdhGetCounterInfo(hCounter, bRetrieveExplainText ? BOOLEAN.TRUE : BOOLEAN.FALSE, ref uiBufferSize, pBuffer);
                        if (status.IsSuccessSeverity())
                        {
                            PDH_COUNTER_INFO.Flat infoRaw = MarshalHelper.PtrToStructure<PDH_COUNTER_INFO.Flat>(pBuffer);

                            info.dwType = infoRaw.dwType;
                            info.CVersion = infoRaw.CVersion;
                            info.CStatus = infoRaw.CStatus;
                            info.lScale = infoRaw.lScale;
                            info.lDefaultScale = infoRaw.lDefaultScale;
                            info.dwUserData = infoRaw.dwUserData;
                            info.dwQueryUserData = infoRaw.dwQueryUserData;
                            info.szFullPath = Marshal.PtrToStringUni(infoRaw.szFullPath);
                            info.CounterPath.szMachineName = Marshal.PtrToStringUni(infoRaw.CounterPath.szMachineName);
                            info.CounterPath.szObjectName = Marshal.PtrToStringUni(infoRaw.CounterPath.szObjectName);
                            info.CounterPath.szInstanceName = Marshal.PtrToStringUni(infoRaw.CounterPath.szInstanceName);
                            info.CounterPath.szParentInstance = Marshal.PtrToStringUni(infoRaw.CounterPath.szParentInstance);
                            info.CounterPath.dwInstanceIndex = infoRaw.CounterPath.dwInstanceIndex;
                            info.CounterPath.szCounterName = Marshal.PtrToStringUni(infoRaw.CounterPath.szCounterName);
                            info.szExplainText = Marshal.PtrToStringUni(infoRaw.szExplainText);
                        }
                    }
                }
            }
            while (status == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException("PdhGetCounterInfo(hCounter = {0}, bRetrieveExplainText = {1}) failed.", hCounter, bRetrieveExplainText);
            return;
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetCounterTimeBase", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhGetCounterTimeBase(
            [In] PDH_HCOUNTER hCounter,
            [Out] out long pTimeBase);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetFormattedCounterArray", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public unsafe static extern PDH_STATUS PdhGetFormattedCounterArray(
            [In] PDH_HCOUNTER hCounter,
            [In] PdhFormat dwFormat,
            [In, Out] ref uint lpdwBufferSize,
            [Out] out uint lpdwBufferCount,
            [In] byte* ItemBuffer);

        /// <summary>
        /// Safe overload for the Win32 PdhGetFormattedCounterArray function that throws an exception on failure.
        /// </summary>
        public static IDictionary<string, PDH_FMT_COUNTERVALUE> PdhGetFormattedCounterArray(
            PDH_HCOUNTER hCounter,
            PdhFormat dwFormat)
        {
            PDH_STATUS status = 0;
            uint uiItemCount;
            uint uiBufferSize;
            byte[] buffer;
            SortedList<string, PDH_FMT_COUNTERVALUE> list = null;

            do
            {
                unsafe
                {
                    /// Get the size needed for the buffer.
                    uiBufferSize = 0;
                    status = PdhGetFormattedCounterArray(hCounter, dwFormat, ref uiBufferSize, out uiItemCount, null);
                    if (status != PDH_STATUS.PDH_MORE_DATA)
                        break;

                    buffer = new byte[uiBufferSize];

                    /// What makes this function difficult is not that string data is packed after the array of values,
                    /// but that it is referenced by absolute pointers inside the struct,
                    /// which means we have to keep alive the pointer to the buffer until we are finished with everything.
                    fixed (byte* pBuffer = buffer)
                    {
                        status = PdhGetFormattedCounterArray(hCounter, dwFormat, ref uiBufferSize, out uiItemCount, pBuffer);
                        if (status.IsSuccessSeverity())
                        {
                            list = new SortedList<string, PDH_FMT_COUNTERVALUE>((int)uiItemCount);

                            UINT_PTR pItem = pBuffer;
                            for (uint uiIndex = 0; uiIndex < uiItemCount; uiIndex++)
                            {
                                PDH_FMT_COUNTERVALUE_ITEM item = MarshalHelper.PtrToStructure<PDH_FMT_COUNTERVALUE_ITEM>(pItem);

                                string strCounterName = Marshal.PtrToStringUni(item.szName);

                                /// Rather than have two parallel PDH_FMT_COUNTERVALUE_ITEMs for marshaling and 
                                /// higher-level work, we just break the raw one down into its logical components,
                                /// also making lookup easier for the caller. A sorted list seemed fastest
                                /// for this kind of short-term data.
                                list.Add(strCounterName, item.FmtValue);

                                pItem += MarshalHelper.SizeOf<PDH_FMT_COUNTERVALUE_ITEM>();
                            }
                        }
                    }
                }
            }
            while (status == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException();

            return list;
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetFormattedCounterValue", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhGetFormattedCounterValue(
            [In] PDH_HCOUNTER hCounter,
            [In] PdhFormat dwFormat,
            [Out] out PerformanceCounterType lpdwType,
            [Out] out PDH_FMT_COUNTERVALUE pValue);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetRawCounterArray", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhGetRawCounterArray(
            [In] PDH_HCOUNTER hCounter,
            [In, Out] ref uint lpdwBufferSize,
            [Out] out uint lpdwItemCount,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] ItemBuffer);

        /// <summary>
        /// Safe overload for the Win32 PdhGetRawCounterArray function that throws an exception on failure.
        /// </summary>
        public static PDH_RAW_COUNTER_ITEM[] PdhGetRawCounterArray([In] PDH_HCOUNTER hCounter)
        {
            PDH_STATUS status;
            uint uiItemCount;
            uint uiBufferSize;
            byte[] buffer = null;

            do
            {
                uiBufferSize = 0;
                status = PdhGetRawCounterArray(hCounter, ref uiBufferSize, out uiItemCount, null);
                if (status != PDH_STATUS.PDH_MORE_DATA)
                    break;

                buffer = new byte[uiBufferSize];
            }
            while ((status = PdhGetRawCounterArray(hCounter, ref uiBufferSize, out uiItemCount, buffer)) == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException();

            return buffer.ArrayFromBytes<PDH_RAW_COUNTER_ITEM>(0, (int)uiItemCount);
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhGetRawCounterValue", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhGetRawCounterValue(
            [In] PDH_HCOUNTER hCounter,
            [Out] out PerformanceCounterType lpdwType,
            [Out] out PDH_RAW_COUNTER pValue);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhIsRealTimeQuery", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool PdhIsRealTimeQuery([In] PDH_HQUERY hQuery);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhLookupPerfIndexByNameW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhLookupPerfIndexByName(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szMachineName,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szNameBuffer,
            [Out] out uint pdwIndex);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhLookupPerfNameByIndexW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhLookupPerfNameByIndex(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szMachineName,
            [In] uint dwNameIndex,
            [Out] StringBuilder szNameBuffer,
            [In, Out] ref uint pcchNameBufferSize);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhMakeCounterPathW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhMakeCounterPath(
            [In] ref PDH_COUNTER_PATH_ELEMENTS.Hydrated pCounterPathElements,
            [Out] StringBuilder szFullPathBuffer,
            [In, Out] ref uint pcchBufferSize,
            [In] PdhMakeCounterPathCode dwFlags);

        /// <summary>
        /// Safe overload for the Win32 PdhMakeCounterPath function that throws an exception on failure.
        /// </summary>
        public static string PdhMakeCounterPath(
            ref PDH_COUNTER_PATH_ELEMENTS.Hydrated pCounterPathElements,
            [In] PdhMakeCounterPathCode dwFlags = 0)
        {
            StringBuilder sb = new StringBuilder(Constants.PDH_MAX_COUNTER_PATH);
            uint uiBufferSize = (uint)sb.Capacity;

            PDH_STATUS status = PdhMakeCounterPath(ref pCounterPathElements, sb, ref uiBufferSize, dwFlags);
            status.TryThrowPdhException();

            return sb.ToString(0, (int)uiBufferSize - 1);
        }

        /// <summary>
        /// Safe overload for the Win32 PdhMakeCounterPath function that throws an exception on failure.
        /// </summary>
        public static string PdhMakeCounterPath(
            string strObjectName,
            string strInstanceName,
            string strCounterName)
        {
            PDH_COUNTER_PATH_ELEMENTS.Hydrated elements = new PDH_COUNTER_PATH_ELEMENTS.Hydrated
            {
                szObjectName = strObjectName,
                szInstanceName = strInstanceName,
                szCounterName = strCounterName,
            };

            return PdhMakeCounterPath(ref elements, 0);
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhOpenQueryW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhOpenQuery(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szDataSource,
            [In] UINT_PTR dwUserData,
            [Out] out PDH_HQUERY phQuery);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhParseCounterPathW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public unsafe static extern PDH_STATUS PdhParseCounterPath(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szFullPathBuffer,
            [In] byte* ItemBuffer,
            [In, Out] ref uint pdwBufferSize,
            [In] uint dwFlags);

        /// <summary>
        /// Safe overload for the Win32 PdhParseCounterPath function that throws an exception on failure.
        /// </summary>
        public static void PdhParseCounterPath(
            string strFullPath,
            out PDH_COUNTER_PATH_ELEMENTS.Hydrated elements)
        {
            PDH_STATUS status = 0;
            uint uiBufferSize;
            byte[] buffer;

            elements = new PDH_COUNTER_PATH_ELEMENTS.Hydrated();

            do
            {
                unsafe
                {
                    /// Get the size needed for the buffer.
                    uiBufferSize = 0;
                    status = PdhParseCounterPath(strFullPath, null, ref uiBufferSize, 0);
                    if (status != PDH_STATUS.PDH_MORE_DATA)
                        break;

                    buffer = new byte[uiBufferSize];

                    /// What makes this function difficult is not that string data is packed after the array of values,
                    /// but that it is referenced by absolute pointers inside the struct,
                    /// which means we have to keep alive the pointer to the buffer until we are finished with everything.
                    fixed (byte* pBuffer = buffer)
                    {
                        status = PdhParseCounterPath(strFullPath, pBuffer, ref uiBufferSize, 0);
                        if (status.IsSuccessSeverity())
                        {
                            PDH_COUNTER_PATH_ELEMENTS.Flat elementsRaw = MarshalHelper.PtrToStructure<PDH_COUNTER_PATH_ELEMENTS.Flat>(pBuffer);

                            elements.szMachineName = Marshal.PtrToStringUni(elementsRaw.szMachineName);
                            elements.szObjectName = Marshal.PtrToStringUni(elementsRaw.szObjectName);
                            elements.szInstanceName = Marshal.PtrToStringUni(elementsRaw.szInstanceName);
                            elements.szParentInstance = Marshal.PtrToStringUni(elementsRaw.szParentInstance);
                            elements.dwInstanceIndex = elementsRaw.dwInstanceIndex;
                            elements.szCounterName = Marshal.PtrToStringUni(elementsRaw.szCounterName);
                        }
                    }
                }
            }
            while (status == PDH_STATUS.PDH_MORE_DATA);
            status.TryThrowPdhException();

            return;
        }

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhRemoveCounter", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhRemoveCounter([In] PDH_HCOUNTER hCounter);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhSetCounterScaleFactor", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhSetCounterScaleFactor(
            [In] PDH_HCOUNTER hCounter,
            [In] int lFactor);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhSetQueryTimeRange", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhSetQueryTimeRange(
            [In] PDH_HQUERY hQuery,
            [In] ref PDH_TIME_INFO pInfo);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhValidatePath", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhValidatePath(
            [In, MarshalAs(UnmanagedType.LPWStr)] string szFullCounterPath);

        [DllImport(Constants.ModuleNames.PDH, EntryPoint = "PdhValidatePathEx", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern PDH_STATUS PdhValidatePathEx(
            [In] PDH_HLOG hDataSource,
            [In, MarshalAs(UnmanagedType.LPWStr)] string szFullPathBuffer);
    }
}
